﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Derbster.Domain.Test
{
    [TestClass]
    public class MatchSimulationUnitTest : ContractUnitTest
    {
        private Match _testMatch;
        private CommandManager _commandManager;

        [TestInitialize]
        public void TestSetup()
        {
            _testMatch = TestHelper.CreateTestMatch(true, true, "TestMatch");
            _commandManager = new CommandManager();
            _commandManager.Clear();
        }

        private Player SelectPlayerFromRoster(Roster roster, int rank)
        {
            return roster.Players.OrderBy(p => p.Skater.Number).Skip(rank - 1).First();
        }

        private void PopulateLineup(Roster roster, Lineup lineup, int jammer, int pivot, params int[] blockers)
        {
            lineup.AddJammer(SelectPlayerFromRoster(roster, jammer));
            lineup.AddPivot(SelectPlayerFromRoster(roster, pivot));
            foreach (var blocker in blockers)
            {
                lineup.AddBlocker(SelectPlayerFromRoster(roster, blocker));
            }
            Assert.IsTrue(lineup.Jammer == SelectPlayerFromRoster(roster, jammer));
            Assert.IsTrue(lineup.Pivot == SelectPlayerFromRoster(roster, pivot));
            foreach (var blocker in blockers)
            {
                Assert.IsTrue(lineup.Blockers.Contains(SelectPlayerFromRoster(roster, blocker)));
            }
        }

        private void PopulateFirstLineupsWithUndoRedo(int jammer, int pivot, params int[] blockers)
        {
            PopulateLineupWithUndoRedo(_testMatch.FirstTeamRoster, _testMatch.CurrentJam.FirstTeamLineup, jammer, pivot, blockers);
        }
        private void PopulateSecondLineupsWithUndoRedo(int jammer, int pivot, params int[] blockers)
        {
            PopulateLineupWithUndoRedo(_testMatch.SecondTeamRoster, _testMatch.CurrentJam.SecondTeamLineup, jammer, pivot, blockers);
        }

        private void PopulateLineupWithUndoRedo(Roster roster, Lineup lineup, int jammer, int pivot, params int[] blockers)
        {
            lineup.AddJammer(SelectPlayerFromRoster(roster, jammer));
            lineup.AddPivot(SelectPlayerFromRoster(roster, pivot));
            _commandManager.Undo();
            _commandManager.Redo();
            foreach (var blocker in blockers)
            {
                lineup.AddBlocker(SelectPlayerFromRoster(roster, blocker));
                _commandManager.Undo();
                _commandManager.Redo();
            }

            Assert.IsTrue(lineup.Jammer == SelectPlayerFromRoster(roster, jammer));
            Assert.IsTrue(lineup.Pivot == SelectPlayerFromRoster(roster, pivot));
            foreach (var blocker in blockers)
            {
                Assert.IsTrue(lineup.Blockers.Contains(SelectPlayerFromRoster(roster, blocker)));
            }
        }

        private void PopulateFirstLineups(int jammer, int pivot, params int[] blockers)
        {
            PopulateLineup(_testMatch.FirstTeamRoster, _testMatch.CurrentJam.FirstTeamLineup, jammer, pivot, blockers);
        }
        private void PopulateSecondLineups(int jammer, int pivot, params int[] blockers)
        {
            PopulateLineup(_testMatch.SecondTeamRoster, _testMatch.CurrentJam.SecondTeamLineup, jammer, pivot, blockers);
        }
        /// <summary>
        /// This is more of a integration test, but this is a walkthrough of 
        /// a simulated match to test that VerifyMatch works. 
        /// </summary>
        [TestMethod]
        public void SimpleMatchUnitTest()
        {
            _testMatch.BeginMatch();
            _testMatch.BeginPeriod();
            PopulateFirstLineups(2,5,7,8,9);
            PopulateSecondLineups(1,3,4,6,7);
            _testMatch.BeginJam();
            _testMatch.CurrentJam.FirstTeamLead();
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(3);
            var player = _testMatch.CurrentJam.FirstTeamLineup.Blockers.First();
 
            _testMatch.SkaterPenalty(player, "B");
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.PlayerSeated(player);
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.PlayerReleased(player);
            _testMatch.CurrentJam.FirstTeamCalledJam();
            _testMatch.EndJam();

            PopulateFirstLineups(3, 6, 7, 8, 9);
            PopulateSecondLineups(2, 3, 6, 7, 8);
            _testMatch.BeginJam();
            _testMatch.CurrentJam.SecondTeamLead();
            _testMatch.CurrentJam.SecondTeamScore(5);
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.CurrentJam.SecondTeamCalledJam();
            _testMatch.EndJam();

            _testMatch.EndPeriod();
            _testMatch.BeginPeriod();

            PopulateFirstLineups(2, 5, 7, 8, 9);
            PopulateSecondLineups(1, 3, 4, 6, 7);
            _testMatch.BeginJam();
            _testMatch.CurrentJam.FirstTeamLead();
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(3);
             player = _testMatch.CurrentJam.FirstTeamLineup.Blockers.First();

            _testMatch.SkaterPenalty(player, "C");
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.PlayerSeated(player);
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.PlayerReleased(player);
            _testMatch.CurrentJam.FirstTeamCalledJam();
            _testMatch.EndJam();

            PopulateFirstLineups(3, 6, 7, 8, 9);
            PopulateSecondLineups(2, 3, 6, 7, 8);
            _testMatch.BeginJam();
            _testMatch.CurrentJam.SecondTeamLead();
            _testMatch.CurrentJam.SecondTeamScore(5);
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.CurrentJam.SecondTeamCalledJam();
            _testMatch.EndJam();

            _testMatch.EndPeriod();
            _testMatch.EndMatch();
            Assert.IsTrue(_testMatch.VerifyMatch());
        }

        [TestMethod]
        public void SimpleMatchUnitTestWithUndoRedo()
        {
            _testMatch.BeginMatch();
            _testMatch.BeginPeriod();
            Assert.IsTrue(_testMatch.HasStarted);
            Assert.IsTrue(_testMatch.PeriodInProgress);
            _commandManager.Undo();
            _commandManager.Undo();
            Assert.IsFalse(_testMatch.HasStarted);
            Assert.IsFalse(_testMatch.PeriodInProgress);
            _commandManager.Redo();
            _commandManager.Redo();
            PopulateFirstLineupsWithUndoRedo(2, 5, 7, 8, 9);
            PopulateSecondLineupsWithUndoRedo(1, 3, 4, 6, 7);
            _testMatch.BeginJam();
            _testMatch.CurrentJam.FirstTeamLead();
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(3);
            _commandManager.Undo();
            _commandManager.Undo();
            _commandManager.Redo();
            _commandManager.Redo();
            Assert.AreEqual(4,_testMatch.CurrentJam.FirstTeamScoring.Sum(sp => sp.Score));
            Assert.AreEqual(3,_testMatch.CurrentJam.SecondTeamScoring.Sum(sp => sp.Score));
            var player = _testMatch.CurrentJam.FirstTeamLineup.Blockers.First();
            _testMatch.SkaterPenalty(player, "B");
            Assert.IsTrue(_testMatch.Penalties.Any(p=>p.Player == player && p.Code == "B"));
            _commandManager.Undo();
            _commandManager.Undo();
            _commandManager.Undo();
            Assert.AreEqual(0,_testMatch.CurrentJam.FirstTeamScoring.Sum(sp => sp.Score));
            Assert.AreEqual(0,_testMatch.CurrentJam.SecondTeamScoring.Sum(sp => sp.Score));
            Assert.IsTrue(!_testMatch.Penalties.Any(p => p.Player == player && p.Code == "B"));
            _commandManager.Redo();
            _commandManager.Redo();
            _commandManager.Redo();
            Assert.AreEqual(4,_testMatch.CurrentJam.FirstTeamScoring.Sum(sp => sp.Score));
            Assert.AreEqual(3,_testMatch.CurrentJam.SecondTeamScoring.Sum(sp => sp.Score));
            Assert.IsTrue(_testMatch.Penalties.Any(p => p.Player == player && p.Code == "B"));
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.PlayerSeated(player);
            Assert.AreEqual(8,_testMatch.CurrentJam.FirstTeamScoring.Sum(sp => sp.Score));
            Assert.AreEqual(7,_testMatch.CurrentJam.SecondTeamScoring.Sum(sp => sp.Score));
            Assert.IsTrue(player.State == PlayerState.InBox);
            _commandManager.Undo();
            _commandManager.Undo();
            _commandManager.Undo();
            Assert.AreEqual(4,_testMatch.CurrentJam.FirstTeamScoring.Sum(sp => sp.Score));
            Assert.AreEqual(3,_testMatch.CurrentJam.SecondTeamScoring.Sum(sp => sp.Score));
            Assert.IsTrue(player.State == PlayerState.InQueue);
            _commandManager.Redo();
            _commandManager.Redo();
            _commandManager.Redo();
            Assert.AreEqual(8,_testMatch.CurrentJam.FirstTeamScoring.Sum(sp => sp.Score));
            Assert.AreEqual(7,_testMatch.CurrentJam.SecondTeamScoring.Sum(sp => sp.Score));
            Assert.IsTrue(player.State == PlayerState.InBox);
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.PlayerReleased(player);
            Assert.AreEqual(12,_testMatch.CurrentJam.FirstTeamScoring.Sum(sp => sp.Score));
            Assert.AreEqual(11,_testMatch.CurrentJam.SecondTeamScoring.Sum(sp => sp.Score));
            Assert.IsTrue(player.State == PlayerState.Ready);
            _testMatch.CurrentJam.FirstTeamCalledJam();
            Assert.IsTrue(_testMatch.CurrentJam.FirstTeamScoring.Called);
            _commandManager.Undo();
            Assert.IsFalse(_testMatch.CurrentJam.FirstTeamScoring.Called);
            _commandManager.Redo();
            Assert.IsTrue(_testMatch.CurrentJam.FirstTeamScoring.Called);
            _testMatch.EndJam();
            Assert.IsFalse(_testMatch.JamInProgress);
            Assert.IsTrue(_testMatch.CurrentJamNumber == 2);
            _commandManager.Undo();
            Assert.IsTrue(_testMatch.JamInProgress);
            Assert.IsTrue(_testMatch.CurrentJamNumber == 1);
            _commandManager.Redo();
            Assert.IsFalse(_testMatch.JamInProgress);
            Assert.IsTrue(_testMatch.CurrentJamNumber == 2);

            PopulateFirstLineups(3, 6, 7, 8, 9);
            PopulateSecondLineups(2, 3, 6, 7, 8);
            _testMatch.BeginJam();
            _testMatch.CurrentJam.SecondTeamLead();
            _testMatch.CurrentJam.SecondTeamScore(5);
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.CurrentJam.SecondTeamCalledJam();
            Assert.AreEqual(16,_testMatch.FirstTeamTotalScore);
            Assert.AreEqual(20,_testMatch.SecondTeamTotalScore);
            _testMatch.EndJam();
            Assert.IsFalse(_testMatch.JamInProgress);
            Assert.IsTrue(_testMatch.CurrentJamNumber == 3);
            _testMatch.EndPeriod();
            _commandManager.Undo();
            _commandManager.Undo();
            Assert.AreEqual(1, _testMatch.CurrentPeriod);
            Assert.IsTrue(_testMatch.JamInProgress);
            Assert.IsTrue(_testMatch.CurrentJamNumber == 2);
            _commandManager.Redo();
            _commandManager.Redo();
            Assert.AreEqual(2, _testMatch.CurrentPeriod);
            Assert.IsFalse(_testMatch.JamInProgress);
            Assert.IsTrue(_testMatch.CurrentJamNumber == 1);
            _testMatch.BeginPeriod();
            Assert.IsTrue(_testMatch.CurrentPeriod == 2);
            Assert.IsTrue(_testMatch.CurrentJamNumber == 1);

            PopulateFirstLineups(2, 5, 7, 8, 9);
            PopulateSecondLineups(1, 3, 4, 6, 7);
            _testMatch.BeginJam();
            Assert.IsTrue(_testMatch.CurrentPeriod == 2);
            Assert.IsTrue(_testMatch.CurrentJamNumber == 1);
            _testMatch.CurrentJam.FirstTeamLead();
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(3);
            Assert.AreEqual(20,_testMatch.FirstTeamTotalScore);
            Assert.AreEqual(23, _testMatch.SecondTeamTotalScore);
            player = _testMatch.CurrentJam.FirstTeamLineup.Blockers.First();

            _testMatch.SkaterPenalty(player, "C");
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.PlayerSeated(player);
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.PlayerReleased(player);
            _testMatch.CurrentJam.FirstTeamCalledJam();
            Assert.AreEqual(28, _testMatch.FirstTeamTotalScore);
            Assert.AreEqual(31, _testMatch.SecondTeamTotalScore);
            _testMatch.EndJam();
            Assert.IsTrue(_testMatch.CurrentPeriod == 2);
            Assert.IsTrue(_testMatch.CurrentJamNumber == 2);
            PopulateFirstLineups(3, 6, 7, 8, 9);
            PopulateSecondLineups(2, 3, 6, 7, 8);
            _testMatch.BeginJam();
            _testMatch.CurrentJam.SecondTeamLead();
            _commandManager.Undo();
            Assert.IsFalse(_testMatch.CurrentJam.SecondTeamScoring.Lead);
            _commandManager.Redo();
            Assert.IsTrue(_testMatch.CurrentJam.SecondTeamScoring.Lead);
            _testMatch.CurrentJam.SecondTeamScore(5);
            _testMatch.CurrentJam.FirstTeamScore(4);
            _testMatch.CurrentJam.SecondTeamScore(4);
            _testMatch.CurrentJam.SecondTeamCalledJam();
            _testMatch.EndJam();
            Assert.AreEqual(32, _testMatch.FirstTeamTotalScore);
            Assert.AreEqual(40, _testMatch.SecondTeamTotalScore);
            _testMatch.EndPeriod();
            _testMatch.EndMatch();
            Assert.AreEqual(4, _testMatch.Jams.Count());
            Assert.AreEqual(1, _testMatch.Jams.Count(j => j.Period == 1 && j.Number == 1));
            Assert.AreEqual(1, _testMatch.Jams.Count(j => j.Period == 1 && j.Number == 2));
            Assert.AreEqual(0, _testMatch.Jams.Count(j => j.Period == 1 && j.Number == 3));
            Assert.AreEqual(1, _testMatch.Jams.Count(j => j.Period == 2 && j.Number == 1));
            Assert.AreEqual(1, _testMatch.Jams.Count(j => j.Period == 2 && j.Number == 2));
            Assert.AreEqual(0, _testMatch.Jams.Count(j => j.Period == 2 && j.Number == 3));
            Assert.IsTrue(_testMatch.VerifyMatch());
        }
    }
}
